package com.ctg.behavior.calc.impala.calculator;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.ctg.behavior.api.CtCalcStatus;
import com.ctg.behavior.calc.common.CtPathNode;
import com.ctg.behavior.calc.common.CtSQLConsts;
import com.ctg.behavior.calc.common.CtSQLParam;
import com.ctg.behavior.calc.impala.CtCalculator;
import com.ctg.behavior.calc.impala.builder.userpath.CtUserPathCalcNodeBuilder;
import com.ctg.behavior.calc.impala.builder.userpath.CtUserPathResultBuilder;
import com.ctg.behavior.calc.impala.builder.userpath.CtUserPathUserIdsReportBuilder;
import com.ctg.behavior.calc.impala.engine.ImpalaSQLEngine;
import com.ctg.behavior.calc.impala.report.userpath.CtUserPathUserIdsReport;
import com.ctg.behavior.calc.impala.transfer.userpath.CtUserPathTotalTransfer;
import com.ctg.behavior.calc.impala.transfer.userpath.CtUserPathUserIdsTransfer;
import com.ctg.behavior.calc.utils.CtCalcSQLUtils;
import com.ctg.behavior.client.impala.common.CtQueryResult;
import com.ctg.behavior.common.calc.CtCalcBody;
import com.ctg.behavior.common.calc.CtCalcResult;
import com.ctg.behavior.common.consts.CtCalcTaskConsts;
import com.ctg.behavior.common.userpath.CtUserPathCalcBody;
import com.ctg.behavior.common.userpath.CtUserPathCalcData;
import com.ctg.behavior.common.userpath.CtUserPathCalcResult;
import com.ctg.behavior.utils.CtDateUtils;
import com.ctg.behavior.utils.CtStatusUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class CtUserPathCalculator implements CtCalculator {

	@Autowired
	private CtUserPathUserIdsReportBuilder userIdsReportBuilder;
	@Autowired
	private CtUserPathUserIdsTransfer userIdsTransfer;
	@Autowired
	private CtUserPathTotalTransfer totalTransfer;

	@Autowired
	private CtUserPathCalcNodeBuilder calcNodeBuilder;
	@Autowired
	private CtUserPathResultBuilder resultBuilder;
	@Autowired
	private ImpalaSQLEngine sqlEngine;

	/****************************************************
	 * @apiNote: 1.
	 * @apiNote: 2.
	 */
	@Override
	public CtCalcResult doCalculator(CtCalcBody calcBody) {
		CtUserPathCalcResult calcResult = new CtUserPathCalcResult();
		if (!(calcBody instanceof CtUserPathCalcBody)) {
			log.error("calcBody={}", calcBody.getClass());
			log.error("calcBody={}", JSON.toJSONString(calcBody));
			CtStatusUtils.fillStatus(calcResult, CtCalcStatus.BodyNoMatch);
			return calcResult;
		}
		CtUserPathCalcBody userPathCalcBody = (CtUserPathCalcBody) calcBody;
		log.info("calcBody={}", JSON.toJSONString(userPathCalcBody));

		CtUserPathCalcData calcData = new CtUserPathCalcData();
		calcData.setAppId(userPathCalcBody.getAppId());
		calcData.setBookmarkId(userPathCalcBody.getBookmarkId());
		calcData.setRequestId(userPathCalcBody.getRequestId());
		calcData.setDataUpdateTime(CtDateUtils.dateToString(new Date()));
		calcData.setReportUpdateTime(CtDateUtils.dateToString(new Date()));
		calcResult.setValue(calcData);

		log.info("time-start: {}", CtDateUtils.getCurrentDateSSS());
		try {
			resultBuilder.resetResultBuilderThreadLocal();
			CtPathNode virtualRootNode = new CtPathNode();
			// 源事件(起始事件或结束事件)
			String sourceEventId = userPathCalcBody.getSourceEvent().getEventId();
			this.doRecursionCalculator(virtualRootNode, sourceEventId, userPathCalcBody);
			calcData = resultBuilder.doBuilder(calcData);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		log.info("time-end: {}", CtDateUtils.getCurrentDateSSS());

		log.info("calcResult={}", JSON.toJSONString(userPathCalcBody));
		return calcResult;
	}

	public void doRecursionCalculator(CtPathNode upperNode, String eventId, CtUserPathCalcBody calcBody) throws Exception {

		// 本级列编号(ColumnIndex)
		Integer theColumnIndex = upperNode.getColumnIndex() + 1;

		log.info("timeRecu-start: {}, {}", CtDateUtils.getCurrentDateSSS(), theColumnIndex);

		// Step1: userId级别SQL, 用于限定userId范围
		CtUserPathUserIdsReport userIdsReport = null;
		String theUserIdsSQL = null;
		{
			userIdsReport = userIdsReportBuilder.doBuilder(calcBody, eventId, theColumnIndex, upperNode.getUserIdsSQL());
			if (userIdsReport.getInitEvent()) {
				if (userIdsReport.getTheColumnDate().compareTo(calcBody.getToDate()) > 0) {
					// 源事件类型为：(起始事件) --> 超过时间限制,不再递归计算路径节点
					return;
				}
			} else {
				if (userIdsReport.getTheColumnDate().compareTo(calcBody.getFromDate()) < 0) {
					// 源事件类型为：(结束事件) --> 超过时间限制,不再递归计算路径节点
					return;
				}
			}
			theUserIdsSQL = userIdsTransfer.doTransfer(userIdsReport, CtSQLParam.createParam());
			log.info("userIdsSQL={}", theUserIdsSQL);
			if (CtCalcSQLUtils.isEmptyCondtions(theUserIdsSQL)) {
				log.error("userIdsSQL is empty.");
				return;
			}
		}
		// Step2: 计算总人数(留存+流失)
		List<CtPathNode> theColumnNodes = new ArrayList<>();
		{
			CtSQLParam sqlParam = CtSQLParam.createParam();
			sqlParam.addParam(CtSQLConsts.KEY_FILTER_SQL, theUserIdsSQL);
			String totalSQL = totalTransfer.doTransfer(userIdsReport, sqlParam);
			log.info("totalSQL={}", totalSQL);
			CtQueryResult totalResult = sqlEngine.doQuery(CtCalcTaskConsts.TASK_TYPE_PATHANALYTICS, totalSQL);
			log.info("totalResult={}", JSON.toJSONString(totalResult));
			if (!totalResult.isSuccess()) {
				log.error("totalResult={}", JSON.toJSONString(totalResult));
				// 执行IMPALA计算异常，直接结束递归
				throw new Exception(totalResult.getMsg());
			}
			// 如果没有查询结果，则直接返回上层
			if (totalResult.getRows().isEmpty()) {
				return;
			}

			// 列举出本次计算中的该列[theColumnIndex]的节点信息
			theColumnNodes = calcNodeBuilder.doBuilder(theColumnIndex, theUserIdsSQL, totalResult.getRows(), calcBody.getByFields());
			// 把每次计算的节点存入ThreadLocal中
			resultBuilder.doNodeBuilder(upperNode, theColumnNodes, calcBody.getByFields());
		}
		// Step3. 递归执行下层节点
		for (CtPathNode nextUpperNode : theColumnNodes) {
			if (nextUpperNode.getColumnIndex() >= (calcBody.getColLimit() - 1)) {
				// 如果路径层达到限制层数，则不再计算本节点的下级
				continue;
			}
			if (nextUpperNode.getTotal() <= 0) {
				// 如果upperNode的计算总数为空，则不需要再向下级计算
				continue;
			}
			for (String nextEventId : calcBody.getEventIds()) {
				this.doRecursionCalculator(nextUpperNode, nextEventId, calcBody);
			}
		}

		log.info("timeRecu-start: {}, {}", CtDateUtils.getCurrentDateSSS(), theColumnIndex);
	}

}
